// SPDX-License-Identifier: GPL-2.0 or GPL-3.0
// Copyright © 2019 Ariadne Devos

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sHT/logic/failbit.h>
#include <sHT/lex/ipv4-addr.h>

#define BADIP UINT32_C(0)

#define success(a) sHT_success_const(a)
#define failure(a) sHT_fail_const(a)

static const struct testcase
{
	uint8_t m[32];
	size_t length;
	sHT_with_failbit
	size_t end;
	uint32_t ip;
} cases[] = {
	/* mappend, first digit, last digit*/
	{ "_0.0.0.19", 8, success(8), UINT32_C(0x00000001) },
	{ "_1.0.0.79", 8, success(8), UINT32_C(0x01000007) },
	{ "_2.0.0.29", 8, success(8), UINT32_C(0x02000002) },
	{ "_9.0.0.39", 8, success(8), UINT32_C(0x09000003) },

	/* large digits */
	{ "_255.250.199.99", 15, success(16), UINT32_C(0xfffac763) },
	{ "_99.255.250.199", 15, success(16), UINT32_C(0x63fffac7) },
	{ "_199.99.255.250", 15, success(16), UINT32_C(0xc763fffa) },
	{ "_199.99.99.2500", 15, success(15), UINT32_C(0xc763fffa) },

	{ "_256.250.199.99", 15, failure(15), BADIP },
	{ "_255.260.199.99", 15, failure(15), BADIP },
	{ "_255.300.199.99", 15, failure(15), BADIP },
	{ "_25@.250.199.99", 15, failure(3), BADIP },
	{ "_255.250.199.@9", 15, failure(13), BADIP },
	{ "_255@250.199.99", 15, failure(4), BADIP },

	/* leading zeroes */
	{ "_01.23.23.23", 12, failure(12), BADIP },
	{ "_10.023.23.23", 13, failure(13), BADIP },
	{ "_10.003.23.23", 13, failure(13), BADIP },
	{ "_10.000.23.23", 13, failure(13), BADIP },
	{ "_10.0000.23.23", 14, failure(14), BADIP },

	/* overly many repetitions */
	{ "_255.250.199.99.20", 18, success(16), UINT32_C(0xfffac763) },
	{ "_99.255.250.199.20", 18, success(16), UINT32_C(0x63fffac7) },
	{ "_199.99.255.250.20", 18, success(16), UINT32_C(0xc763fffa) },
	{ "_199.99.99.2500.20", 18, success(15), UINT32_C(0xc763fffa) },

	{ "_255.250.199.99@20", 18, success(16), UINT32_C(0xfffac763) },
	{ "_99.255.250.199@20", 18, success(16), UINT32_C(0x63fffac7) },
	{ "_199.99.255.250@20", 18, success(16), UINT32_C(0xc763fffa) },
	{ "_199.99.99.2500@20", 18, success(15), UINT32_C(0xc763fffa) },

	/* off-by-one digit tests */
	{ "_0.0.0.0", 8, success(8), UINT32_C(0x00000000) },
	{ "_1.0.0.0", 8, success(8), UINT32_C(0x01000000) },
	{ "_2.0.0.0", 8, success(8), UINT32_C(0x02000000) },
	{ "_9.0.0.0", 8, success(8), UINT32_C(0x09000000) },
	{ "_/.0.0.0", 8, failure(1), BADIP },
	{ "_:.0.0.0", 8, failure(1), BADIP },
	{ "_1/.0.0.0", 9, failure(2), BADIP },
	{ "_1:.0.0.0", 9, failure(2), BADIP },
	{ "_10/.0.0.0", 10, failure(3), BADIP },
	{ "_10:.0.0.0", 10, failure(3), BADIP },

	/* Check 2 > length - i.

	   (The trailing octet is not within specified bounds,
	   so it may not be parsed, as is the leading '0')

	   In case of 1 > length - i, the first digit of the last octet
	   is accessed out-of-bounds, but that's ‘corrected’ by
	   sHT_index_nospec to the first byte of the string*/
	{ "01.2.3.", 7, failure(7), BADIP },
	{ "01.2.30.", 7, failure(7), BADIP },
	{ "01.2.30.", 7, failure(7), BADIP },
	{ "010.2.3.", 8, failure(8), BADIP },
	{ "010.2.30.", 8, failure(8), BADIP },
	{ "010.20.3.", 9, failure(9), BADIP },
	{ "010.20.30.", 9, failure(9), BADIP },
	{ "010.20.25.", 10, failure(10), BADIP },
	{ "010.20.255.", 10, failure(10), BADIP },
	{ "010.20.254.", 11, failure(11), BADIP },
	{ "010.20.255.", 11, failure(11), BADIP },
	{ "010.20.256.", 11, failure(11), BADIP },

	/* too early EOF */
	{ "_0.0.255.", 9, failure(10), BADIP },
	{ "_0.0.20.", 8, failure(9), BADIP },
	{ "_20.0.0.", 8, failure(9), BADIP },
	{ "_0.10.255", 9, failure(10), BADIP },
	{ "_0.10.20", 8, failure(9), BADIP },
	{ "_20.10.0", 8, failure(9), BADIP },

	/* empty */
	{ "0", 1, failure(1), BADIP },
	{ "_", 1, failure(1), BADIP },
	{ "_0", 2, failure(2), BADIP },
	{ "_0.", 3, failure(3), BADIP },
	{ "_0.1", 4, failure(4), BADIP },
	{ "_0.12", 5, failure(5), BADIP },
	{ "_0.12.", 6, failure(6), BADIP },
	{ "_0.12.2", 7, failure(8), BADIP },
};

#define ARRAY_SIZE(a) ((sizeof(a) / sizeof(*(a))))

#ifdef __FRAMAC__
#  define _Noreturn
#endif

_Noreturn void
fail(size_t i)
{
	if (printf("FAIL: ipv4 (%d)\n", (unsigned) i) < 0)
		exit(2);
	exit(1);
}

int
main(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
		struct s2_ipv4_maybe ret;
		ret = s2_parse_ipv4(cases[i].length, cases[i].m);
		/* out-of-bounds */
		if (sHT_fail_value(ret.end) > cases[i].length)
			fail(i);
		if (sHT_bad(ret.end)) {
			if (sHT_good(cases[i].end))
				fail(i);
			/* (z): *at least* up to (>, not =) fail_value(ret.end), [0-9.] */
			if (sHT_fail_value(ret.end) > sHT_fail_value(cases[i].end))
				fail(i);
			goto compare_longer_inputs;
		}

		/* Synthetise input from output, check with reference input*/
		char ipcheck[32];
		if (sHT_fail_value(ret.end) - 1 != (size_t) sprintf(ipcheck, "%d.%d.%d.%d", ret.ip >> 24, (ret.ip >> 16) & 0xff, (ret.ip >> 8) & 0xff, ret.ip & 0xff))
			/* length does not agree */
			fail(i);
		if (memcmp(cases[i].m + 1, ipcheck, sHT_fail_value(ret.end) - 1)) {
			/* bytes do not agree */
			fail(i);
		}

	compare_longer_inputs:
		for (size_t d = sHT_fail_value(ret.end) + 1; d <= sHT_fail_value(cases[i].length); d++) {
			struct s2_ipv4_maybe alt = s2_parse_ipv4(d, cases[i].m);
			/* Check failbit for equality */
			if ((ret.end ^ alt.end) & sHT_failbit_limit)
				fail(i);
			/* Check parsing doesn't end too late */
			if (sHT_fail_value(alt.end) <= sHT_fail_value(cases[i].end))
			/* If at all, there is only one way to parse it. */
			if (sHT_good(ret.end) && (ret.end != alt.end))
				fail(i);
			/* Check numeric IPv4Address */
			if (sHT_good(ret.end) && (ret.ip != alt.ip))
				fail(i);
		}
	}
	if (printf("PASS: ipv4\n") < 2)
		exit(2);
	return 0;
}
